package nachos.threads;


/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>,
 * and multiple threads can be waiting to <i>listen</i>. But there should never
 * be a time when both a speaker and a listener are waiting, because the two
 * threads can be paired off at this point.
 */
public class Communicator {
    /**
     * Allocate a new communicator.
     */
    public Communicator() {
    	lock = new Lock();
    	speakCon = new Condition(lock);
    	listenCon = new Condition(lock);
    	speakNum = 0;
    	listenNum = 0;
    	communicating = false;
    }

    /**
     * Wait for a thread to listen through this communicator, and then transfer
     * <i>word</i> to the listener.
     *
     * <p>
     * Does not return until this thread is paired up with a listening thread.
     * Exactly one listener should receive <i>word</i>.
     *
     * @param	word	the integer to transfer.
     */
    public void speak(int word) {
    	lock.acquire();
    	speakNum ++;
    	while(communicating || listenNum == 0)
    		speakCon.sleep();
    	communicating = true;
    	buf = word;
    	listenNum --;
    	listenCon.wake();
    	lock.release();
    }

    /**
     * Wait for a thread to speak through this communicator, and then return
     * the <i>word</i> that thread passed to <tt>speak()</tt>.
     *
     * @return	the integer transferred.
     */    
    public int listen() {
    	lock.acquire();
    	listenNum ++;
    	if (!communicating && speakNum > 0)
    		speakCon.wake();
    	listenCon.sleep();
    	int word = buf;
    	speakNum --;
    	communicating = false;
    	speakCon.wake();
    	lock.release();
    	return word;
    }
    
    Lock lock;
    Condition speakCon, listenCon;
    boolean communicating;
    int speakNum, listenNum; 
    int buf;
    
    
    private static class ListenTest implements Runnable{
		public ListenTest(Communicator _c) {
			c = _c;
		}

		@Override
		public void run() {
			System.out.println(KThread.currentThread().getName() + " running.....");
			System.out.println("receive message : " + c.listen());
			System.out.println(KThread.currentThread().getName() + " finished.....");
		}
		Communicator c = null;
    }
    
    private static class SpeakTest implements Runnable{
		public SpeakTest(Communicator _c) {
			c = _c;
		}

		@Override
		public void run() {
			System.out.println(KThread.currentThread().getName() + " running.....");
			c.speak(9);
			System.out.println(KThread.currentThread().getName() + " finished");
		}
		Communicator c = null;
    }
    
    public static void selfTest(){
    	Communicator c = new Communicator();
    	KThread listener1 = new KThread(new ListenTest(c)).setName("Listener1");
    	listener1.fork();
    	KThread listener2 = new KThread(new ListenTest(c)).setName("Listener2");
    	listener2.fork();
    	KThread speaker1 = new KThread(new SpeakTest(c)).setName("Speaker1");
    	speaker1.fork();
    	KThread speaker2 = new KThread(new SpeakTest(c)).setName("Speaker2");
    	speaker2.fork();
    	listener1.join();
    	listener2.join();
    	speaker1.join();
    	speaker2.join();
    	return;
    }
    
}
